﻿using JetBrains.Annotations;
using Orchard;
using Orchard.Environment.Configuration;
using Orchard.FileSystems.AppData;
using Orchard.Logging;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Configuration;

namespace Fineout.ConfigShellSettings.Environment.Configuration
{
    [UsedImplicitly]
    public class ConfigShellSettingsManager : Component, IShellSettingsManager
    {
        private const string SettingsFileName = "Settings.txt";
        private readonly IAppDataFolder _appDataFolder;
        private readonly IShellSettingsManagerEventHandler _events;

        public ConfigShellSettingsManager(
            IAppDataFolder appDataFolder,
            IShellSettingsManagerEventHandler events)
        {
            _appDataFolder = appDataFolder;
            _events = events;
        }

        public IEnumerable<ShellSettings> LoadSettings()
        {
            Logger.Debug("Reading ShellSettings...");
            var settingsList = LoadSettingsInternal().ToArray();

            var tenantNames = (from settings in settingsList
                               select settings.Name)
                .ToArray();

            Logger.Debug("Returning {0} ShellSettings objects for tenants {1}.",
                         tenantNames.Count(),
                         String.Join(", ", tenantNames));

            return settingsList;
        }

        public void SaveSettings(ShellSettings settings)
        {
            // Settings must be specified
            if (settings == null)
                throw new ArgumentNullException("settings");

            // Name of the tenant - must be specified
            var tenant = settings.Name;
            if (String.IsNullOrEmpty(settings.Name))
                throw new ArgumentException(
                    "The Name property of the supplied ShellSettings object is null or empty; the settings cannot be saved.",
                    "settings");

            Logger.Debug("Saving ShellSettings for tenant '{0}' to Web.config AppSettings...", tenant);

            // Load Web.config AppSettings section
            var config = WebConfigurationManager.OpenWebConfiguration("~");
            var appSettings = config.AppSettings;

            // Track whether any change actually made to Web.config app settings
            var change = false;

            // Format tenant prefix like "Orchard.[TENANT NAME]:"
            var prefix = "Orchard." + tenant + ":";

            // Iterate shell settings
            var keys = settings.Keys.ToArray();
            foreach (var key in keys)
            {
                // Format app setting key name as tenant prefix followed by shell setting key 
                var keyName = prefix + key;

                // If setting exists, update; else, create new
                if (appSettings.Settings.AllKeys.Contains(keyName))
                {
                    // Skip if the app setting value has not changed
                    if (settings[key] == appSettings.Settings[keyName].Value)
                        continue;

                    // Update the app setting with new value
                    appSettings.Settings[keyName].Value = settings[key];
                    change = true;
                }
                else
                {
                    // Create new app setting
                    appSettings.Settings.Add(keyName, settings[key]);
                    change = true;
                }
            }

            // Only save Web.config if any change actually made (saving file causes the application pool to recycle)
            if (change)
            {
                config.Save();
                Logger.Debug("Web.config saved successfully with AppSettings for tenant '{0}'.", settings.Name);
            }
            else
                Logger.Debug("Web.config AppSettings unchanged for tenant '{0}'; no need to save file.", settings.Name);


            ////////////////////////////////////////////////////////////////////////////////
            //// CODE BELOW DUPLICATED FROM ShellSettingsManager ///////////////////////////
            //TODO: (?) Get instance of ShellSettingsManager to pass off to explicit implementation of IShellSettingsManager.SaveSettings(ShellSettings)

            Logger.Debug("Saving ShellSettings (Settings.txt) for tenant '{0}'...", settings.Name);
            var filePath = Path.Combine(Path.Combine("Sites", settings.Name), SettingsFileName);
            _appDataFolder.CreateFile(filePath, ShellSettingsSerializer.ComposeSettings(settings));

            Logger.Debug("ShellSettings (Settings.txt) saved successfully; flagging tenant '{0}' for restart.", settings.Name);
            _events.Saved(settings);
        }

        private IEnumerable<ShellSettings> LoadSettingsInternal()
        {
            var settings = ConfigurationManager.AppSettings;

            // Match all "Sites/[TENANT NAME]/Settings.txt" files to load tenant names
            var tenantNames = _appDataFolder
                .ListDirectories("Sites")
                .SelectMany(path => _appDataFolder.ListFiles(path))
                .Where(path => String.Equals(Path.GetFileName(path), SettingsFileName, StringComparison.OrdinalIgnoreCase))
                .Select(path => Path.GetFileName(Path.GetDirectoryName(path)));

            foreach (var tenant in tenantNames)
            {
                // AppSettings key prefix for this tenant ("Orchard.[TENANT NAME]")
                var prefix = "Orchard." + tenant;

                // Match AppSettings keys having this prefix
                var keys = settings.AllKeys
                                   .Where(k => k.IndexOf(prefix, StringComparison.Ordinal) == 0)
                                   .ToArray();

                // If no keys with this prefix, no settings for this tenant; so, continue...
                if (!keys.Any())
                    continue;

                // Compose serialized shell settings text using AppSettings for matched keys
                var sb = new StringBuilder();
                foreach (var key in keys)
                {
                    var keyName = key.Substring(prefix.Length + 1);
                    sb.AppendLine(
                        keyName + ShellSettingsSerializer.Separator + " "
                        + (settings[key] ?? ShellSettingsSerializer.EmptyValue));
                }

                // Pass off to ShellSettingsSerializer to parse settings text
                yield return ShellSettingsSerializer.ParseSettings(sb.ToString());
            }
        }
    }
}
